<?php

namespace app\resources\service;

use help\SystemHelp;
use app\resources\model\Nodes;
use app\resources\tasks\NodeTask;
use app\resources\model\NodesFrps;
use app\resources\model\NodesPort;
use app\resources\model\WebSocket;
use app\resources\validate\NodeValidate;

/**
 * 服务基类
 */
class NodeService extends BaseService
{

    // 
    public function __construct()
    {
        $this->model = new Nodes;
    }

    /**
     * 创建或更新
     *
     * @param NodeValidate $param
     * @return array
     */
    public function createOrUpdate(array $param)
    {
        $node = $this->model::findOrEmpty($param['id']);
        $isUpdateDomains = $node->domains != $param['domains'];
        $node->password = $param['password'] = $param['password'] ?: $node->password;
        $node = $node->fill($param);
        $node->password = $param['password'] ?: $node->password;
        if (!($param['id'] ?? '')) {
            $node->loading = '部署中';
            $server = env('APP_HOST');
            $cmd = "curl -sSL $server/system/node/install | sh";
        } else {
            $cmd = "echo 1";
        }
        $res = SystemHelp::sshCmd($node->host, $node->username, $node->password, $cmd);
        if ($res) {
            throw new \think\Exception('失败，请检查节点配置是否正确！');
        }

        // 添加节点服务器frps数据
        if ($node->save() && $isUpdateDomains) {
            $this->syncFrpsData($node);
            $this->syncFrpsConfig($node);
        }

        return $node->toArray();
    }

    /**
     * 同步frps数据
     *
     * @param Nodes $node
     * @return void
     */
    public function syncFrpsData(Nodes $node)
    {
        $domains = explode("\n", $node->domains);
        // 先删后插
        $nodesFrps = NodesFrps::where('nid', $node->id)->whereNotIn('subdomain_host', $domains)->select();
        foreach ($nodesFrps as $nodesFrps) {
            NodesPort::where('nid', $node->id)
                ->where('port', $nodesFrps->bind_port)
                ->whereOr('port', $nodesFrps->vhost_http_port)
                ->whereOr('port', $nodesFrps->vhost_https_port)
                ->whereOr('port', $nodesFrps->dashboard_port)
                ->delete();
            $nodesFrps->delete();
        }
        // 插入
        foreach ($domains as $domain) {
            $isExists = NodesFrps::where('nid', $node->id)->where('subdomain_host', $domain)->value('id');
            if ($domain && !$isExists) {
                $prot = $node->getProt();
                NodesPort::create(['nid' => $node->id, 'port' => $prot]);
                $dashboard_port = $node->getProt();
                NodesPort::create(['nid' => $node->id, 'port' => $dashboard_port]);
                NodesFrps::create([
                    'nid' => $node->id,
                    'bind_port' => $prot,
                    'vhost_http_port' => $prot,
                    'vhost_https_port' => $prot,
                    'token' => $node->getSecretKey(),
                    'subdomain_host' => $domain,
                    'privilege_allow_ports' => '80,443,' . Nodes::MIN_PROT . '-' . Nodes::MAX_PROT,
                    'max_pool_count' => 100000,
                    'dashboard_port' => $dashboard_port,
                    'dashboard_user' => NodesFrps::DASHBOARD_USER,
                    'dashboard_pwd' => NodesFrps::DASHBOARD_PWD,
                    'state' => 1
                ]);
            }
        }
    }

    /**
     * 同步frps配置
     *
     * @param Nodes $node
     * @return void
     */
    public function syncFrpsConfig(Nodes $node)
    {
        $result = WebSocket::pushNodeCmd([
            'uid' => $node->host,
            'msg' => [
                'type' => 'cmd:host',
                'body' => $node->getCmd()
            ]
        ]);
        if ($result == false) {
            throw new \think\Exception('请求超时！');
        }
        if (($result['out'][0] ?? '') != 'success') {
            throw new \think\Exception($result['out'][0] ?? '配置同步失败');
        }
    }

    /**
     * 重置节点
     *
     * @param int $id
     * @return void
     */
    public function reset(int $id)
    {
        $node = $this->model::find($id);
        if (!$node) {
            throw new \think\Exception('节点不存在！');
        }
        if ($node->loading) {
            // throw new \think\Exception('节点繁忙！');
        }
        $res = SystemHelp::sshCmd($node->host, $node->username, $node->password, "echo 1");
        if ($res) {
            throw new \think\Exception('请求失败，请检查节点配置是否正确！');
        }
        app('swoole.server')->task([NodeTask::class, [
            'type' => 'reset',
            'data' => $id
        ]]);
        $node->loading = '正在重置';
        $node->save();
    }

    /**
     * 节点上线
     *
     * @param string $host
     * @return void
     */
    public function online(string $host)
    {
        $node = Nodes::where('host', $host)->find();
        if ($node) {
            $node->is_line = 1;
            $node->cc = $node->cc + 1;
            $node->loading = '';
            if ($node->save()) {
                try {
                    $this->syncFrpsConfig($node->id);
                } catch (\Throwable $th) {
                    //throw $th;
                }
            }
        }
    }


    /**
     * 节点离线
     *
     * @param string $host
     * @return void
     */
    public function offline(string $host)
    {
        $node = Nodes::where('host', $host)->find();
        if ($node) {
            $node->is_line = 0;
            $node->loading = '';
            $node->save();
        }
    }


    /**
     * 重启动所有frps
     *
     * @param integer $nid 
     * @return void
     */
    public function restartAllFrps(int $nid)
    {
        $node = $this->model::find($nid);
        if (!$node) {
            throw new \think\Exception('节点不存在！');
        }
        WebSocket::pushNodeCmd([
            'uid' => $node->host,
            'msg' => [
                'type' => 'cmd:host',
                'body' => <<<EOF
                    for file in /usr/frps/config/*
                    do
                    if [ \$(echo \$file | grep '.ini') ];then
                        nohup /usr/frps/frps -c \$file >/dev/null 2>&1 &
                    fi
                    done   
                    EOF
            ]
        ], false);
    }
}
